using System;
using System.Windows.Forms;
using System.ComponentModel;
using System.Windows.Interop;
using System.Xml.Serialization;
using System.Runtime.InteropServices;
using FrontDisplay.NativeMethods;

namespace FrontDisplay
{
    public class Hotkey
	{
		private static int _currentId;
		private const int MaximumId = 0xBFFF;
		
		private Keys _keyCode;
        private bool _shift;
        private bool _control;
        private bool _alt;
		private bool _windows;

		[XmlIgnore]
        private int _id;

		[XmlIgnore]
        private bool _registered;

        [XmlIgnore]
        private System.Windows.Window _windowControl;

        [XmlIgnore]
        private IntPtr _windowHandle;

		public event HandledEventHandler Pressed;

		public Hotkey() : this(Keys.None, false, false, false, false) { }

        public Hotkey(HotkeyInfo info) : this(info.Key, info.Shift, info.Ctrl, info.Alt, info.Win) { }

        public Hotkey(HotkeyInfo info, Action action) : this(info)
        {
            Action = action;
        }
        
        public Hotkey(System.Windows.Input.Key keyCode, bool shift, bool control, bool alt, bool windows) : 
            this((Keys)System.Windows.Input.KeyInterop.VirtualKeyFromKey(keyCode), shift, control, alt, windows) { }

        public Hotkey(Keys keyCode, bool shift, bool control, bool alt, bool windows)
        {
            KeyCode = keyCode;
            Shift = shift;
            Control = control;
            Alt = alt;
            Windows = windows;

            // Register us as a message filter
            //TODO: Look into it
            //Application.AddMessageFilter(this);
        }

        ~Hotkey()
		{
			// Unregister the hotkey if necessary
			if (Registered)
			{
			    Unregister();
			}
        }

        private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            // Only process WM_HOTKEY messages
            if (msg != WinApi.WM_HOTKEY)
            {
                return IntPtr.Zero;
            }

            // Check that the ID is our key and we are registerd
            if (_registered && (wParam.ToInt32() == _id))
            {
                // Fire the event and pass on the event if our handlers didn't handle it
                handled = OnPressed();
            }
            return IntPtr.Zero;
        }


		public Hotkey Clone()
		{
			// Clone the whole object
			return new Hotkey(_keyCode, _shift, _control, _alt, _windows);
		}

        //public bool GetCanRegister(IntPtr hWnd)
        //{
        //    // Handle any exceptions: they mean "no, you can't register" :)
        //    try
        //    {
        //        // Attempt to register
        //        if (!Register(hWnd /*windowControl*/))
        //        {
        //            return false;
        //        }

        //        // Unregister and say we managed it
        //        Unregister();
        //        return true;
        //    }
        //    catch (Win32Exception) { return false; }
        //    catch (NotSupportedException) { return false; }
        //}

		public bool Register(System.Windows.Window windowControl)
        {
            //if (!GetCanRegister(wndHelper.Handle))
            //{
            //    //NOTE: Looks like attempts to register will fail or throw an exception, show an error/visual user feedback
            //    return false;
            //}

            if (_registered) return false;

            WindowInteropHelper wndHelper = new WindowInteropHelper(windowControl);

            HwndSource fromHwnd = HwndSource.FromHwnd(wndHelper.Handle);
            if (fromHwnd != null) fromHwnd.AddHook(WndProc);

            //// Check that we have not registered
            //if (_registered)
            //{
            //    throw new NotSupportedException("You cannot register a hotkey that is already registered");
            //}

            // We can't register an empty hotkey
            if (Empty)
            {
                throw new NotSupportedException("You cannot register an empty hotkey");
            }

            // Get an ID for the hotkey and increase current ID
            _id = _currentId;
            _currentId = _currentId + 1 % MaximumId;

            // Translate modifier keys into unmanaged version
            uint modifiers = (Alt ? WinApi.MOD_ALT : 0) | (Control ? WinApi.MOD_CONTROL : 0) |
                            (Shift ? WinApi.MOD_SHIFT : 0) | (Windows ? WinApi.MOD_WIN : 0);

            try
            {
                // Register the hotkey
                if (WinApi.RegisterHotKey(wndHelper.Handle, _id, modifiers, (int) _keyCode) == 0)
                {
                    // Is the error that the hotkey is registered?
                    if (Marshal.GetLastWin32Error() == WinApi.ERROR_HOTKEY_ALREADY_REGISTERED)
                    {
                        //TODO: save record in log
                        return false;
                    }
                    //throw new Win32Exception();
                }
            }
            catch (Win32Exception) { return false; }
            catch (NotSupportedException) { return false; }

		    // Save the control reference and register state
            _registered = true;
            _windowControl = windowControl;
            _windowHandle = wndHelper.Handle;

            // We successfully registered
            return true;
        }

		public void Unregister()
		{
			// Check that we have registered
			if (!_registered)
			{
			    return;
			    //throw new NotSupportedException("You cannot unregister a hotkey that is not registered");
			}
        
			// It's possible that the control itself has died: in that case, no need to unregister!
            //if (_windowControl != null)
            {
                // Clean up after ourselves
                if (WinApi.UnregisterHotKey(_windowHandle, _id) == 0)
                {
    		        throw new Win32Exception();
	    	    }
            }

            if (_windowControl != null)
            {
                WindowInteropHelper wndHelper = new WindowInteropHelper(_windowControl);
                if (wndHelper.Handle != IntPtr.Zero)
                {
                    HwndSource hwndSource = HwndSource.FromHwnd(wndHelper.Handle);
                    if (hwndSource != null) hwndSource.RemoveHook(WndProc);
                }
            }
            
            // Clear the control reference and register state
			_registered = false;
			_windowControl = null;
            _windowHandle = IntPtr.Zero;
		}

		private void Reregister()
		{
			// Only do something if the key is already registered
			if (!_registered)
			{
			    return;
			}

			// Save control reference
			System.Windows.Window windowControl = _windowControl;

			// Unregister and then reregister again
			Unregister();
            Register(windowControl);
		}

        internal bool OnPressed()
		{
			// Fire the event if we can
			HandledEventArgs handledEventArgs = new HandledEventArgs(false);
			if (Pressed != null)
			{
			    Pressed(this, handledEventArgs);
			}
            if (Action != null)
            {
                Action();
                return true;
            }

			// Return whether we handled the event or not
			return handledEventArgs.Handled;
		}

        public override string ToString()
        {
			// We can be empty
			if (Empty)
			{
			    return "(none)";
			}

			// Build key name
			string keyName = Enum.GetName(typeof(Keys), _keyCode);
			switch (_keyCode)
			{
				case Keys.D0:
				case Keys.D1:
				case Keys.D2:
				case Keys.D3:
				case Keys.D4:
				case Keys.D5:
				case Keys.D6:
				case Keys.D7:
				case Keys.D8:
				case Keys.D9:
					// Strip the first character
					keyName = keyName.Substring(1);
					break;
				default:
					// Leave everything alone
					break;
			}

            // Build modifiers
            string modifiers = string.Empty;
            if (_shift)
            {
                modifiers += "Shift+";
            }
            if (_control)
            {
                modifiers += "Control+";
            }
            if (_alt)
            {
                modifiers += "Alt+";
            }
			if (_windows)
			{
			    modifiers += "Windows+";
			}

			// Return result
            return modifiers + keyName;
        }

		public bool Empty
		{
			get { return _keyCode == Keys.None; }
		}

		public bool Registered
		{
			get { return _registered; }
		}

        public Keys KeyCode
        {
            get { return _keyCode; }
            set
			{
				_keyCode = value;
				Reregister();
			}
        }

        public bool Shift
        {
            get { return _shift; }
            set 
			{
				_shift = value;
				Reregister();
			}
        }

        public bool Control
        {
            get { return _control; }
            set
			{ 
				_control = value;
				Reregister();
			}
        }

        public bool Alt
        {
            get { return _alt; }
            set
			{ 
				_alt = value;
				Reregister();
			}
        }

		public bool Windows
		{
			get { return _windows; }
			set 
			{
				_windows = value;
				Reregister();
			}
		}

        public HotkeyInfo Value
        {
            get
            {
                return new HotkeyInfo
                {
                    Win = _windows,
                    Ctrl = _control,
                    Alt = _alt,
                    Shift = _shift,
                    Key = System.Windows.Input.KeyInterop.KeyFromVirtualKey((int) _keyCode)
                };
            }
            set
            {
                if ((_windows == value.Win) &&
                    (_control == value.Ctrl) &&
                    (_alt == value.Alt) &&
                    (_shift == value.Shift) &&
                    (_keyCode == (Keys) System.Windows.Input.KeyInterop.VirtualKeyFromKey(value.Key)))
                {
                    return;
                }

                _windows = value.Win;
                _control = value.Ctrl;
                _alt = value.Alt;
                _shift = value.Shift;
                _keyCode = (Keys) System.Windows.Input.KeyInterop.VirtualKeyFromKey(value.Key);
                Reregister();
            }
        }

        public Action Action
        {
            get; set;
        }
	}
}
